#include "mop.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <libgen.h>

// stat a file/directory/link: -1 on error, 0 on success
inline static int mop_stat(struct stat *stats, const char *path);
// analyze a file to decide if it should be added to the output
inline static void mop_analyze(const char *name, struct stat *stat);
// add a new result (block memory allocation)
//static void mop_add_result(const char *name);
// output the results
//static void mop_output_results(void);
// perform the expected actions on obsolete files
inline static void mop_undertake_action(const char *name);
// Printout the number of obsolete files and the total memory
// occupied in a multiple of bytes
static void mop_print_memory(void);

#ifdef NO_RECURSION
void mop_run(void) {
  char **stack;
  char *stack_entry;
  char prefix[512];
  char fullname[512];
  DIR  *dir;
  register int k;
  int stack_index = 0;
  int length;
  struct stat stats;
  struct dirent *entry;

  // Build a stack of strings
  stack = (char **)calloc(MOP_STACK_SIZE, sizeof(char *));

  // Push (by copy)
  stack[stack_index++] = strdup(input.path);
 
#ifdef DEBUG
  printf("Invoking mop_run from path: %s\n", input.path);
#endif

  while (stack_index > 0) {
    stack_entry = stack[--stack_index];
    if (mop_stat(&stats, stack_entry) < 0) {
      continue;
    }
    // Directory stats are ignored for the moment
    if (S_ISDIR(stats.st_mode)) {
      dir = opendir(stack_entry);
      if (!dir) {
        fprintf(stderr, "cannot open dir: %s, error: ", stack_entry);
        perror("");
        continue;
      }
      length = strlen(stack_entry);
      strncpy(prefix, stack_entry, length);
      prefix[length]   = '/';
      prefix[length+1] = '\0';

      for(;;) {
        entry = readdir(dir);
        if (!entry) { break; }
        if ((entry->d_type == DT_REG) || (entry->d_type == DT_LNK)) {
          if ((entry->d_type == DT_LNK) && (input.flags & MOP_IGNORE_SYMLINKS)) {
            continue;
          }
          if ((entry->d_name[0] == '.') && (input.flags & MOP_IGNORE_HIDDEN)) {
            continue;
          }
          strcpy(fullname, prefix);
          strcat(fullname, entry->d_name);
          strcat(fullname, "\0");
          if (mop_stat(&stats, fullname) < 0) {
            continue;
          }
          mop_analyze(fullname, &stats);
        }
        else if (entry->d_type == DT_DIR) {
          // Skip special directories '.' and '..'
          if (entry->d_name[0] == '.') {
            if (input.flags & MOP_IGNORE_HIDDEN) {
              continue;
            }
            if ((entry->d_name[1] == '\0')|| (entry->d_name[1] == '.')) {
              continue;
            }
          }
          strcpy(fullname, prefix);
          strcat(fullname, entry->d_name);
          strcat(fullname, "\0");
          stack[stack_index++] = strdup(fullname);
        }
        else {
          continue;
        }
      }
      closedir(dir);
      if ((input.action == MOP_TRASH) || (input.action == MOP_DELETE)) {
        // Attempt to remove the directory (does nothing if not empty)
        if (!(input.flags & MOP_NO_DIRS) && !rmdir(stack_entry)) {
          printf ("Removed directory: %s\n", stack_entry);
        }
      }
    }
    else if (S_ISREG(stats.st_mode) || S_ISLNK(stats.st_mode)) {
      mop_analyze(stack_entry, &stats);
    }
    else {
      // Ignore special files
      continue;
    }
  } // end stack

  for (k = 0; k < MOP_STACK_SIZE; k++) {
    free(stack[k]);
  }
  free(stack);

//  mop_undertake_actions();
//  mop_output_results();
  if (!(input.flags & MOP_QUIET)) {
    mop_print_memory();
  }
}

#else

// Recursive version
// static char *recursion_path;
void mop_visit(const char *path);

void mop_run(void) {
//  recursion_path = input.path;
  mop_visit(input.path);
  if (!(input.flags & MOP_QUIET)) {
    mop_print_memory();
  }
}

void mop_visit(const char *path) {
  // Recursion specific
  char prefix[512];
  DIR  *dir;
  /////////////////////
  static char fullname[512];
  static int length;
  static struct stat stats;
  static struct dirent *entry;

  if (mop_stat(&stats, path) < 0) {
    return;
  }

  // Directory stats are ignored for the moment
  if (S_ISDIR(stats.st_mode)) {
    dir = opendir(path);
    if (!dir) {
      fprintf(stderr, "cannot open dir: %s, error: ", path);
      perror("");
      return;
    }
    length = strlen(path);
    strncpy(prefix, path, length);
    prefix[length]   = '/';
    prefix[length+1] = '\0';

    for(;;) {
      entry = readdir(dir);
      if (!entry) { break; }
      if ((entry->d_type == DT_REG) || (entry->d_type == DT_LNK)) {
        if ((entry->d_type == DT_LNK) && (input.flags & MOP_IGNORE_SYMLINKS)) {
          continue;
        }
        if ((entry->d_name[0] == '.') && (input.flags & MOP_IGNORE_HIDDEN)) {
          continue;
        }
        strcpy(fullname, prefix);
        strcat(fullname, entry->d_name);
        strcat(fullname, "\0");
        if(mop_stat(&stats, fullname) < 0) {
          continue;
        }
        mop_analyze(fullname, &stats);
      }
      else if (entry->d_type == DT_DIR) {
        // Skip special directories '.' and '..'
        if (entry->d_name[0] == '.') {
          if (input.flags & MOP_IGNORE_HIDDEN) {
            continue;
          }
          if ((entry->d_name[1] == '\0')|| (entry->d_name[1] == '.')) {
            continue;
          }
        }
        strcpy(fullname, prefix);
        strcat(fullname, entry->d_name);
        strcat(fullname, "\0");
        mop_visit(strdup(fullname));
      }
      else {
        continue;
      }
    } // End for each entry of the dir
    closedir(dir);
    if ((input.action == MOP_TRASH) || (input.action == MOP_DELETE)) {
      // Attempt to remove the directory (does nothing if not empty)
      if (!(input.flags & MOP_NO_DIRS) && !rmdir(path)) {
        printf ("Removed directory: %s\n", path);
      }
    }
  }
  else if (S_ISREG(stats.st_mode) || S_ISLNK(stats.st_mode)) {
    mop_analyze(path, &stats);
  }
  else {
    // Ignore special files
    return;
  }
  
  return;
}
#endif

inline
static int mop_stat(struct stat *stats, const char *path) {
  static int    status;
  status = stat(path, stats);
  if (status < 0) {
    //char *errormsg = calloc(strlen(path)+32, sizeof(char));
    fprintf(stderr,"stat error on file %s: ", path);
    perror("");
    if (input.flags & MOP_FATAL) {
      fprintf(stderr,"interrupting execution, as requested by the user\n");
      mop_finish(EXIT_FAILURE);
    }
  }
  return(0);
}


inline
static void mop_analyze(const char *name, struct stat *stat) {
  static double diff_access;
  static double diff_modif;
  static char *buffer_date;
  static int length;
#ifdef DEBUG
  printf ("Ref time: %s", ctime(&input.time));
  printf ("%s\naccess time: %smodif time: %s", name, ctime(&stat->st_atime), ctime(&stat->st_mtime));
#endif
  diff_access = difftime(stat->st_atime, input.time);
  diff_modif  = difftime(stat->st_mtime, input.time);

#ifdef DEBUG
  printf ("diff_access: %lf\ndiff_modif: %lf\n", diff_access, diff_modif);
#endif

  if ((diff_access <= 0) && (diff_modif <= 0)) {
    output.memory += (unsigned long)stat->st_size;
    output.nfiles ++;
#ifdef DEBUG
    printf("Kept\n");
#endif
//    mop_add_result(name);
    mop_undertake_action(name);
    if (!(input.flags & MOP_QUIET)) {
      if (input.flags & MOP_SHOW_TIME) {
#ifndef DEBUG
        if (diff_access > diff_modif) {
#endif
          buffer_date = ctime(&stat->st_atime);
          length = strlen(buffer_date);
          buffer_date[length - 1] = '\0';
          printf("[A");
#ifdef DEBUG
          printf(" diff=%lf", diff_access);
#endif
          printf("] [%s] ", buffer_date);
#ifndef DEBUG
        }
        else {
#endif
          buffer_date = ctime(&stat->st_mtime);
          length = strlen(buffer_date);
          buffer_date[length - 1] = '\0';
          printf("[M");
#ifdef DEBUG
          printf(" diff=%lf", diff_modif);
#endif
          printf("] [%s] ", buffer_date);
#ifndef DEBUG
        }
#endif
      }
      printf("%s\n", name);
    }
  }
}

/*static void mop_add_result(const char *name) {
  output.index++;

  // allocate a new block
  if (output.index >= output.n_entries) {
    output.n_entries += MOP_OUT_BLOCK_SIZE;
    output.entries = (char **)realloc(
          output.entries, output.n_entries * sizeof(char *));
  }

  output.entries[output.index] = strdup(name);
}


static void mop_output_results(void) {
  register int k;
  printf ("\n");
  for (k = 0; k <= output.index; k++) {
    printf ("%s\n", output.entries[k]);
  }
}
*/


inline
static void mop_undertake_action(const char *name) {
  static char path[512];
//  register int k;
//  for (k = 0; k <= output.index; k++) {
    switch (input.action) {
      case MOP_NOTHING:
        break;
      case MOP_TRASH:
        {
          strcpy(path, input.trash_path);
          strcat(path, "/");
          strcat(path, basename(strdup(name)));//output.entries[k]));
          strcat(path, "\0");
          link(name/*output.entries[k]*/, path);
          unlink(name);//output.entries[k]);
          printf("Moved file: %s to %s\n", name/*output.entries[k]*/, input.trash_path);
        }
        break;
      case MOP_DELETE:
        {
          unlink(name);//output.entries[k]);
          printf("Removed file: %s\n", name);//output.entries[k]);
        }
        break;
      default:
        break;
    }
  //}
}


static void mop_print_memory (void) {
  printf("Number of obsolete files: %ld\n", output.nfiles);
  printf("Total memory: ");
  if (output.memory > (unsigned long)1073741824) {
    // print in gigabytes
    printf("%.2lf GB     (%lu bytes)\n", (double)output.memory / (double)(1024*1024*1024), output.memory);
  }
  else if (output.memory > 1048576) {
    printf("%.2lf MB     (%lu bytes)\n", (double)output.memory / (double)(1024*1024), output.memory);
    // print in megabytes
  }
  else if (output.memory > 1024) {
    printf("%.2lf KB     (%lu bytes)\n", (double)output.memory / 1024.0, output.memory);
    // print in kilobytes
  }
  else {
    printf("%lu bytes\n", output.memory);
    // print in bytes
  }
}

